package com.ease.component.managers {
   import com.ease.component.constant.LoadingStateName;
   import com.ease.component.interfaces.IShowLoadingState;

   import flash.display.DisplayObjectContainer;
   import flash.events.Event;
   import flash.utils.Dictionary;

   import mx.core.Application;
   import mx.core.FlexGlobals;
   import mx.core.UIComponent;
   import mx.events.FlexEvent;
   import mx.events.MoveEvent;
   import mx.events.ResizeEvent;
   import mx.managers.PopUpManager;
   import com.ease.component.controls.ProgressCanvas;

   public class ProgressManager {
      public function ProgressManager(single:Forbidden) {}

      // style constant definition.
      public static const LOADING_BAR:String = "loadingBar";
      public static const LOADING_SPINNER:String = "loadingSpinner";

      // padding for progress indicator.
      public static const PADDING:int = 1;

      /**
       * Map to store the progress indicator, the key is the
       * view which needs to show progress.
       */
      private static var progresses:Dictionary = new Dictionary();

      /**
       * Check if the view has a IShowLoadingState parent and return it if true.
       */
      private static function getParentShowLoadingStateComponent(view:UIComponent):IShowLoadingState {
         var busyComponent:DisplayObjectContainer = view;
         while (busyComponent) {
            if (busyComponent is IShowLoadingState) {
               return IShowLoadingState(busyComponent);
            }
            busyComponent = busyComponent.parent;
         }
         return null;
      }

      public static function showProgress(view:UIComponent, loadingStyle:String = LOADING_SPINNER):void {
         var busyComponent:IShowLoadingState = getParentShowLoadingStateComponent(view);
         if (busyComponent) {
            busyComponent.loadingState = LoadingStateName.LOADING;
            return;
         }
         if (view && !progresses[view]) {
            var progressCanvas:ProgressCanvas = new ProgressCanvas();
            progressCanvas.width = view.width - 2 * PADDING;
            progressCanvas.height = view.height - 2 * PADDING;
            progressCanvas.currentState = loadingStyle;

            progresses[view] = progressCanvas;

            view.addEventListener(ResizeEvent.RESIZE, onViewSizeChanged);
            view.addEventListener(Event.REMOVED_FROM_STAGE, onViewRemove);

            var popup:Function = function():void {
               PopUpManager.addPopUp(progressCanvas, view);
               PopUpManager.centerPopUp(progressCanvas);
            };

            view.callLater(popup);
         }
      }

      /**
       * Remove progress indicator from the view, here first remove the
       * view size change event listener, then delete the related progress
       * indicator from the map.
       *
       * If the view is contained in a IShowLoadingState component,
       * we'll find this component first and let this component to show
       * the loaded state.
       *
       * @param view - the view needs to remove the progress indicator.
       */
      public static function hideProgress(view:UIComponent):void {
         var busyComponent:IShowLoadingState = getParentShowLoadingStateComponent(view);
         if (busyComponent) {
            busyComponent.loadingState = LoadingStateName.LOADED;
            return;
         }
         if (view && progresses[view]) {
            view.removeEventListener(ResizeEvent.RESIZE, onViewSizeChanged);
            view.removeEventListener(Event.REMOVED_FROM_STAGE, onViewRemove);

            var removePopup:Function = function():void {
               PopUpManager.removePopUp(progresses[view]);
               delete progresses[view];
            };

            view.callLater(removePopup);
         }
      }

      /**
       * Hide progress indicator if view is removed from stage.
       * @param event - remove event.
       *
       */
      private static function onViewRemove(event:Event):void {
         var view:UIComponent = event.target as UIComponent;
         hideProgress(view);
      }

      /**
       * Update the progress indicator's position and size based on
       * the view's position and size.
       * @param event - the ResizeEvent of the related view.
       *
       */
      private static function onViewSizeChanged(event:ResizeEvent):void {
         var view:UIComponent = event.target as UIComponent;
         var progressCanvas:ProgressCanvas = progresses[view];

         relocateProgressCanvas(view, progressCanvas);
      }

      /**
       * Update progress indicator's position and size.
       * @param view - the view
       * @param progressCanvas - the canvas in which progress indicator is.
       *
       */
      private static function relocateProgressCanvas(view:UIComponent, progressCanvas:ProgressCanvas):void {
         if (progressCanvas) {
            var updatePosition:Function = function():void {
               PopUpManager.centerPopUp(progressCanvas);
            }

            progressCanvas.width = view.width - 2 * PADDING;
            progressCanvas.height = view.height - 2 * PADDING;

            progressCanvas.callLater(updatePosition);
         }
      }

      /**
       * @private
       * Caller of addAppListenerToVisibleChanged function.
       */
      private static var listenToVisibleOrPositionEvent:Boolean = addAppListenerToVisibleOrPositionChanged();

      /**
       * Add show/hide component event listener to <code>Application</code>.
       * @return - a flag used by caller, no meaning here.
       *
       */
      private static function addAppListenerToVisibleOrPositionChanged():Boolean {
         var app:Application = FlexGlobals.topLevelApplication as Application;

         // listen the event in its whole liftcycle.
         app.addEventListener(FlexEvent.SHOW, elementVisibleChanged, true);
         app.addEventListener(FlexEvent.HIDE, elementVisibleChanged, true);
         app.addEventListener(FlexEvent.SHOW, elementVisibleChanged);
         app.addEventListener(FlexEvent.HIDE, elementVisibleChanged);
         // listen the move event.
         app.systemManager.addEventListener(MoveEvent.MOVE, elementPositionChanged, true);

         return true;
      }

      /**
       * Listener for view's position change event, mainly used to update progress
       * indicator's position.
       * @param event - <code>MoveEvent</code>.
       *
       */
      private static function elementPositionChanged(event:MoveEvent):void {
         var container:UIComponent = event.target as UIComponent;

         if (container == null) {
            return;
         }

         for (var view:* in progresses) {
            if (container.contains(view)) {
               relocateProgressCanvas(view, progresses[view]);
            }
         }
      }

      /**
       * Listener for view's visible change event, i.e. <code>FlexEvent.SHOW</code>
       * and <code>FlexEvent.HIDE</code>. The main function here is update the progress
       * indicator's visible based on view's visible.
       * @param event - <code>FlexEvent.SHOW</code> or <code>FlexEvent.HIDE</code>
       *
       */
      private static function elementVisibleChanged(event:FlexEvent):void {
         var container:UIComponent = event.target as UIComponent;

         if (container == null) {
            return;
         }

         for (var view:* in progresses) {
            // need reset the initial value for every views here.
            var visible:Boolean = event.type == FlexEvent.SHOW;

            if (container.contains(view)) {
               // adjust the visible state
               if (visible && view != container) {
                  // get parent view visible
                  visible = viewChainVisible(view.parent, container);

                  if (visible) {// if all parents are visible.
                     visible = view.visible;
                  }
               }

               progresses[view].visible = visible;
            }
         }
      }

      /**
       * Get view's visible state, if one of its parent or itself is
       * unvisible, it means the view is unvisible, else it is visible.
       * @param view - the view needed to get the visible state.
       * @param container - the top parent view which contains view.
       * @return - the view visible flag.
       *
       */
      private static function viewChainVisible(view:*, container:UIComponent):Boolean {
         var visible:Boolean = view.visible;

         if (view == container) {
            return visible;
         } else  {
            visible &&= viewChainVisible(view.parent, container);
         }

         return visible;
      }

   }
}

class Forbidden {

}
